package org.novelhibernate.core;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.novelhibernate.core.transaction.TransactionSupport;


/**
 * This is the nested statement class, used when there is need to build xml like nested JDBC statements
 * @author Tao Chen
 *
 */
public class NestedStatement extends TransactionSupport {
	
	private static final Logger log = Logger.getLogger(NestedStatement.class);


	// This is not thread safe
	private List<ParameterMapper> extraMapper;

	// This is not thread safe
	private List<ParameterMapper> aliasMapper;

	private String sql;

	private Object[] targets;

	// The attribute that will need to get data for children statements
	// if there is need (e.g. when deletion, there is need to query form DB to
	// delete some data that
	// holding the FK)
	private String acquiredName;

	private PreparedStatement prep;

	private List<ArgumentWrapper> wrapper;



	private boolean isRoot = false;

	// Reverse here is for the statement in different level,
	// for those statement within the same level, order would be
	// determined by the order they were setted
	private boolean isReversed = false;

	// SAVE, UPDATE or DELETE
	private int operation;

	// The children statements the needed to be executed before or after the execution of
	// this statement
	private List<NestedStatement> statements;

	public NestedStatement() {

	}

	public NestedStatement(String sql, Object[] targets) {

		this.sql = sql;
		this.targets = targets;
	}

	public NestedStatement(String sql, String acquiredName) {

		this.sql = sql;
		this.acquiredName = acquiredName;
	}

	public NestedStatement setAliasedParameter(int index, String parameter) {

		if (aliasMapper == null)
			aliasMapper = new ArrayList<ParameterMapper>();

		aliasMapper.add(new ParameterMapper(parameter, index));

		return this;

	}

	public NestedStatement setPredefinedParameter(int index, Object parameter)
			throws Throwable {

		if (extraMapper == null)
			extraMapper = new ArrayList<ParameterMapper>();

		ParameterMapper mapper = new ParameterMapper(parameter, index);
		Class<?> clazz = filterToPrimitive(mapper.getParameter().getClass());

		mapper.setPreparedStatementSetMethod(Class.forName("java.sql.PreparedStatement").getMethod(
				"set" + convertToMethodName(clazz.getName()),
				new Class[] { Integer.TYPE, clazz }));
		extraMapper.add(mapper);

		return this;

	}

	public String getSql() {
		return sql;
	}

	public Object[] getTargets() {
		return targets;
	}

	public String getAcquiredName() {
		return acquiredName;
	}

	public List<ParameterMapper> getExtraMapper() {
		return extraMapper;
	}

	public List<ParameterMapper> getAliasMapper() {
		return aliasMapper;
	}

	public List<NestedStatement> getStatements() {
		return statements;
	}

	public PreparedStatement getPrep() {
		return prep;
	}

	public void setPrep(PreparedStatement prep) {
		this.prep = prep;
	}

	public List<ArgumentWrapper> getWrapper() {
		return wrapper;
	}

	public void setWrapper(List<ArgumentWrapper> wrapper) {
		this.wrapper = wrapper;
	}

	public boolean isRoot() {
		return isRoot;
	}

	public void setRoot(boolean isRoot) {
		this.isRoot = isRoot;
	}

	public int getOperation() {
		return operation;
	}

	public void setOperation(int operation) {
		this.operation = operation;
	}

	public void setStatements(List<NestedStatement> statements) {
		this.statements = statements;
	}

	public void setSql(String sql) {
		this.sql = sql;
	}

	public void setTargets(Object[] targets) {
		this.targets = targets;
	}

	public void setAcquiredName(String acquiredName) {
		this.acquiredName = acquiredName;
	}

	public boolean isReversed() {
		return isReversed;
	}

	public void setReversed(boolean isReversed) {
		this.isReversed = isReversed;
	}

	public void execute(List<Object> resultList) throws SQLException {

		executeAll(this, resultList);

	}

	public void release() throws SQLException {

		releaseAll(this);

	}

	private void executeAll(NestedStatement state, List<Object> resultList)
			throws SQLException {

		if (state.isReversed() && state.isRoot()){
			
			log.debug("[Batch SQL]: " + state.getSql());
			state.getPrep().executeBatch();
		}

		if (null != state.getStatements())
			for (NestedStatement statement : state.getStatements()) {

				if (!statement.isReversed()
						&& null != statement.getStatements())
					executeAll(statement, resultList);

				if (null != resultList)
					resultList.add(statement.getPrep().executeBatch());
				else if (null != statement.getPrep()){
					
					log.debug("[Batch SQL]: " + statement.getSql());
					statement.getPrep().executeBatch();
				}
				if (statement.isReversed() && null != statement.getStatements())
					executeAll(statement, resultList);
			}

		if (!state.isReversed() && state.isRoot()){
			
			log.debug("[Batch SQL]: " + state.getSql());
			state.getPrep().executeBatch();
		}
	}

	private void releaseAll(NestedStatement state) throws SQLException {

		if (null != state.getStatements())
			for (NestedStatement statement : state.getStatements()) {

				if (null != statement.getStatements())
					releaseAll(statement);

				if (null != statement.getPrep()) {

					log.debug("[Release resource for batch SQL]: " + statement.getSql());
					statement.getPrep().clearBatch();
					statement.getPrep().close();

				}
			}

		if (state.isRoot()) {

			log.debug("[Release resource for batch SQL]: " + state.getSql());
			state.getPrep().clearBatch();
			state.getPrep().close();
		}

	}

}
